package main

import (
	"errors"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"time"
)

type User struct {
	Name  string
	Age   int
	Score int
	gorm.Model
}

//
//type UserV2 struct {
//	Name string
//}

func main() {
	//1、前提：安装了数据库+有gorm_class这个schema。
	dsn := "root:hedaodao@(127.0.0.1:3306)/gorm_class?charset=utf8mb4&parseTime=True&loc=Local"
	//open函数有两个参数，
	//  -第一个是mysql驱动配置，详情参见https://github.com/go-gorm/mysql
	//  -第二个是gorm的配置，详情参见https://gorm.io/zh_CN/docs/gorm_config.html
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		SkipDefaultTransaction: false,
		NamingStrategy: schema.NamingStrategy{
			TablePrefix:   "hdd_", // table name prefix, table for `User` would be `t_users`
			SingularTable: true,   // use singular table name, table for `User` would be `user` with this option enabled
		},
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	if err != nil {
		panic(err)
	}
	//2、连接池。gorm使用Go原生的database/sql维护连接池
	mysqlDB, err := db.DB() //通过DB方法获取*sql.db
	mysqlDB.SetMaxOpenConns(500)
	mysqlDB.SetMaxIdleConns(10)
	mysqlDB.SetConnMaxLifetime(time.Hour)
	defer mysqlDB.Close()

	//3、将Go数据结构迁移到数据库中（创建一个表,将表和结构体建立映射）
	//文档地址：https://gorm.io/zh_CN/docs/migration.html
	//如果已存在数据库表规则：User结构某字段改名，表中会添加该新字段，原字段对应的表字段不会删除；结构体删除字段，表也不会删除该字段；
	//AutoMigrate自动创建创建
	db.AutoMigrate(&User{})

	//想要配置迁移细节，使用Migrator,返回一个Migrator接口。可查看源码Migrator接口中提供的函数签名
	//m := db.Migrator()

	//含义：如果存在就删除，如果不存在就创建
	//if m.HasTable(&User{}) {
	//	//m.DropTable(&User{})删除表
	//	m.RenameTable(&User{}, &UserV2{}) //将表重命名为第二个字段。可以是字符串直接指定表名，但是Go就失去了对这个表的控制了，所以一般指定一个新的结构体
	//} else {
	//	m.CreateTable(&User{})
	//}

	//4、插入数据
	//4-1 插入一条数据，使用结构体指针
	//if err := db.Create(&User{
	//	Name: "jack",
	//	Age:  20,
	//}).Error; err != nil {
	//	fmt.Printf("插入数据出错%v\n", err)
	//}
	//4-2 插入多条数据,使用切片
	//if err := db.Create(&[]User{
	//	{Name: "jhon", Age: 20},
	//	{Name: "tom", Age: 21},
	//}).Error; err != nil {
	//	fmt.Printf("插入数据出错%v\n", err)
	//}
	//4-3 Select指定传入Create的数据字段哪些需要插入 ；Omit指定传入Create的数据字段哪些需要忽略
	//if err := db.Select("Name").Create(&User{
	//	Name: "jackv2",
	//	Age:  20,
	//}).Error; err != nil {
	//	fmt.Printf("插入数据出错%v\n", err)
	//}
	//5、查询 (First、Last、Take如果查不到数据就是错误，即ErrRecordNotFound，而Find找不到也不会报错)
	//查询会将结果放入查询函数（First、Last、Take、Find）的参数中（一个结构体指针），这里有一个隐藏的知识点，查询函数会在schema中找到参数这个空结构体对应的表。如果找不到就需要先用Model指定下是哪个表
	//5-1 First、Last，按照主键排序（没有主键，则按照第一个字段进行排序），然后查询第一个、最后一个数据。
	// 只有在目标 struct 是指针或者通过 db.Model() 指定 model 时，该方法才有效。Model指定了当前查询的是User这个模型，即其对应的数据库表
	var u User
	if err := db.Model(&User{}).Where("name=?", "jack3").First(&u).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			fmt.Println("查找不到")
			return
		} else {
			fmt.Println("查询错误，err：", err)
			return
		}
	}
	fmt.Printf("查询的数据：%#v\n", u)

	//5-2 Take 查表内的第一条数据（不必排序）
	//var u User
	//db.Take(&u)
	//fmt.Printf("查询的数据：%#v\n", u)

	//5-3 Find查询多条数据(不设置查询条件，就把整个表格查出来了)
	//var uList []User
	//db.Find(&uList)
	//fmt.Printf("查询的数据：%#v\n", uList)

	//5-4 查询条件（以First例子，添加查询条件）
	//5-4-1 Where函数——字符串参数
	//var u User
	//db.Where("name=?", "tom").First(&u)
	//fmt.Printf("查询的数据：%#v\n", u)
	//5-4-2 Where函数——结构体参数
	//var u User
	//db.Where(User{Name: "tom"}).First(&u)
	//fmt.Printf("查询的数据：%#v\n", u)
	//5-4-3 Where函数——map参数
	//var u User
	//db.Where(map[string]interface{}{
	//	"name": "tom",
	//}).First(&u)
	//fmt.Printf("查询的数据：%#v\n", u)
	//5-4-4 内联查询，即不使用Where函数，可以直接将字符串、结构体、map作为参数传入
	//var u User
	//db.First(&u, "name=?", "tom")
	//fmt.Printf("查询的数据：%#v\n", u)

	//5-5 Or查询条件，参数与Where相同包括：字符串、结构体、map

	//5-6 Not查询条件，参数包括：字符串、结构体、map，还可以是切片
	//var u User
	//db.Not([]int64{1, 2, 3}).First(&u) //主键不在{1、2、3}的集合里
	//fmt.Printf("查询的数据：%#v\n", u)

	//5-7 Order查询条件.desc从大到小，aesc从小到大，默认aesc
	//var u []User
	//db.Order("age desc,score").Find(&u) //如果多个字段排序，就用逗号分隔
	//fmt.Printf("查询的数据：%#v\n", u)
	//5-8 Limit限制返回几条数据，Offset表示查询结果跳过几条数据
	//var u []User
	//db.Limit(3).Find(&u)
	//fmt.Printf("查询的数据：%#v\n", u)
	//db.Offset(3).Find(&u)
	//fmt.Printf("查询的数据：%#v\n", u)
	//5-9 Select指定查询结果的某些字段，Omit忽略查询结果中的某些字段

	//5-10 智能查询（如果多次查询，每次都使用Select指定要哪些字段，有些臃肿）
	//type newUser struct {
	//	Name string
	//	Age  int
	//}
	//var nu []newUser
	//db.Model(&User{}).Find(&nu)   //必须使Model指定查询的表，否则就回去查找new_users这个表了（我这个例子，设置了TablePrefix:"hdd_"这个配置，所以会去找hdd_new_users这个表）
	//fmt.Printf("查询的数据：%#v\n", nu) //查询的数据：main.newUser{Name:"jack", Age:20}

	//6、更新 (要么先查询后，更新查询的结果；要么必须指定where条件才能更新符合条件的数据)
	//6-1、save更新数据（无论如何都会更新，即使是零值也会更新到数据库中）
	//var uList []User
	//res := db.Find(&uList)
	//
	//for key := range uList {
	//	uList[key].Age = 0
	//}
	//res.Save(&uList)

	//6-2、updates
	//6-2-1 参数为结构体(如果某字段是该类型的零值，则不更新该字段)
	//var uList []User
	//db.First(&uList).Updates(User{
	//	Name: "",
	//	Age:  0,
	//})
	//6-2-2 参数为Map（可以更新零值）
	//var uList []User
	//db.First(&uList).Updates(map[string]interface{}{
	//	"name": "", //map的key可以是结构体字段名Name，也可以是数据库字段名name，没影响
	//	"age":  0,
	//})

	//6-3、update 只更新选择的字段
	//var uList []User
	//db.First(&uList).Update("name", "jack")

	//7、删除（要么先查询后，再删除查询的结果；要么必须指定where条件才能删除其中符合条件的数据）
	//7-1、删除(删除是软删除;删除的数据不会放在uList中，这里的uList只是指代删除的结构)
	//var uList []User //这里使用var uList User也可以的
	//db.Where("name=?", "jhon").Delete(&uList)
	//7-2、物理删除
	//var uList []User
	//db.Unscoped().Where("name=?", "").Delete(&uList)

	//8、原生SQL语句，Raw参数是sql语句，Scan执行
	//var uList []User //这里使用var uList User也可以的
	//db.Raw("select * from hdd_users where name=?", "jhon").Scan(&uList)
	//fmt.Printf("%v\n", uList)

}
